﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;
using WpfGraphing.Style;

namespace WpfGraphing.Collections
{
    public class BarChartDataList : ChartDataList
    {
        public event EventHandler<EventArgs> AddChart;

        private ChartBarType barType = ChartBarType.Vertical;

        public ChartBarType BarType
        {
            get { return barType; }
            set { barType = value; }
        }

        public enum ChartBarType
        {
            Vertical = 0,
            Horizontal = 1,
            VerticalStack = 2,
            HorizontalStack = 3,
            VerticalOverlay = 4,
            HorizontalOverlay = 5
        }

        private ChartStyleGridLines _csGridLine;

        double labelMinimumFontSize;
        public double LabelMinimumFontSize
        {
            get { return labelMinimumFontSize; }
            set
            {
                if (value > 1)
                {
                    labelMinimumFontSize = value;
                }
                else
                {
                    labelMinimumFontSize = 1;
                }
            }
        }

        double labelFontSize = 12;
        public double LabelFontSize
        {
            get { return labelFontSize; }
            set
            {
                if (value < 1)
                {
                    labelFontSize = 1;
                }
                else
                {
                    labelFontSize = value;
                }
            }
        }

        bool activeToolTip = false;
        public bool ActiveToolTip
        {
            get { return activeToolTip; }
            set
            {
                activeToolTip = value;
            }
        }

        bool labelValuesVisibility;
        public bool LabelValuesVisibility
        {
            get { return labelValuesVisibility; }
            set
            {
                labelValuesVisibility = value;
            }
        }

        public void CreatGraph(ChartStyleGridLines _csGridLine)
        {
            if (this.AddChart != null)
            {
                _csGridLine.CalculateYMax();
                this.AddChart(null, EventArgs.Empty);                
            }
        }

        public void AddBars(ChartStyleGridLines chartStyleGridLines)
        {
            _csGridLine = chartStyleGridLines;
            if (chartStyleGridLines == null)
            {
                throw new ArgumentNullException(paramName: "chartStyleGridLines", message: "cannot be null");
            }

            int nSeries = DataList.Count;

            switch (BarType)
            {
                case ChartBarType.Vertical:
                    Vertical(chartStyleGridLines: chartStyleGridLines, nSeries: nSeries);
                    break;

                case ChartBarType.VerticalOverlay:
                    VerticalOverlay(chartStyleGridLines: chartStyleGridLines, nSeries: nSeries);
                    break;

                case ChartBarType.VerticalStack:
                    VerticalStack(chartStyleGridLines: chartStyleGridLines, nSeries: nSeries);
                    break;

                case ChartBarType.Horizontal:
                    Horizontal(chartStyleGridLines: chartStyleGridLines, nSeries: nSeries);
                    break;

                case ChartBarType.HorizontalOverlay:
                    HorizontalOverlay(chartStyleGridLines: chartStyleGridLines, nSeries: nSeries);
                    break;

                case ChartBarType.HorizontalStack:
                    HorizontalStack(chartStyleGridLines: chartStyleGridLines, nSeries: nSeries);
                    break;
            }
        }

        void Vertical(ChartStyleGridLines chartStyleGridLines, int nSeries)
        {
            double width;
            if (nSeries == 1)
            {
                foreach (BarDataSeries ds in DataList)
                {
                    ds.Poly.Clear();
                    width = chartStyleGridLines.XTick * ds.BarWidth;
                    for (int i = 0; i < ds.LineSeries.Points.Count; i++)
                    {
                        ds.Poly.Add(DrawVerticalBar(ds.LineSeries.Points[i], chartStyleGridLines, ds, width, 0));

                    }
                }
            }
            else
            {
                int j = 0;
                foreach (BarDataSeries ds in DataList)
                {
                    for (int i = 0; i < ds.LineSeries.Points.Count; i++)
                    {
                        DrawVerticalBar1(ds.LineSeries.Points[i], chartStyleGridLines, ds, nSeries, j);
                    }
                    j++;
                }
            }
        }

        void VerticalOverlay(ChartStyleGridLines chartStyleGridLines, int nSeries)
        {
            double width;
            if (nSeries > 1)
            {
                int j = 0;
                foreach (BarDataSeries ds in DataList)
                {
                    width = chartStyleGridLines.XTick * ds.BarWidth;
                    width = width / Math.Pow(2, j);
                    for (int i = 0; i < ds.LineSeries.Points.Count; i++)
                    {
                        DrawVerticalBar(ds.LineSeries.Points[i], chartStyleGridLines, ds, width, 0);
                    }
                    j++;
                }
            }
        }

        void VerticalStack(ChartStyleGridLines chartStyleGridLines, int nSeries)
        {
            double width;
            if (nSeries > 1)
            {
                List<Point> temp = new List<Point>();
                double[] tempy = new double[DataList[0].LineSeries.Points.Count];


                //Parallel.ForEach(DataList, (dss)=>
                //    {
                //        var ds = dss as BarDataSeries;
                //        width = chartStyleGridLines.XTick * ds.BarWidth;

                //        for (int i = 0; i < ds.LineSeries.Points.Count; i++)
                //        {
                //            if (temp.Count > 0)
                //            {
                //                tempy[i] += temp[i].Y;
                //            }
                //            DrawVerticalBar(ds.LineSeries.Points[i], chartStyleGridLines, ds, width, tempy[i]);
                //       }
                //        temp.Clear();
                //        temp.AddRange(ds.LineSeries.Points);
                //    });



                foreach (BarDataSeries ds in DataList)
                {
                    width = chartStyleGridLines.XTick * ds.BarWidth;

                    for (int i = 0; i < ds.LineSeries.Points.Count; i++)
                    {
                        if (temp.Count > 0)
                        {
                            tempy[i] += temp[i].Y;
                        }
                        DrawVerticalBar(ds.LineSeries.Points[i], chartStyleGridLines, ds, width, tempy[i]);
                    }
                    temp.Clear();
                    temp.AddRange(ds.LineSeries.Points);
                }
            }
        }






        void Horizontal(ChartStyleGridLines chartStyleGridLines, int nSeries)
        {
            double width;
            if (nSeries == 1)
            {
                foreach (BarDataSeries ds in DataList)
                {
                    width = chartStyleGridLines.YTick * ds.BarWidth;
                    for (int i = 0; i < ds.LineSeries.Points.Count; i++)
                    {
                        DrawHorizontalBar(ds.LineSeries.Points[i], chartStyleGridLines, ds, width, 0);
                    }
                }
            }
            else
            {
                int j = 0;
                foreach (BarDataSeries ds in DataList)
                {
                    for (int i = 0; i < ds.LineSeries.Points.Count; i++)
                    {
                        DrawHorizontalBar1(ds.LineSeries.Points[i], chartStyleGridLines, ds, nSeries, j);
                    }
                    j++;
                }
            }
        }

        void HorizontalOverlay(ChartStyleGridLines chartStyleGridLines, int nSeries)
        {
            double width;
            if (nSeries > 1)
            {
                int j = 0;
                foreach (BarDataSeries ds in DataList)
                {
                    width = chartStyleGridLines.YTick * ds.BarWidth;
                    width = width / Math.Pow(2, j);
                    for (int i = 0; i < ds.LineSeries.Points.Count; i++)
                    {
                        DrawHorizontalBar(ds.LineSeries.Points[i], chartStyleGridLines, ds, width, 0);
                    }
                    j++;
                }
            }
        }

        void HorizontalStack(ChartStyleGridLines chartStyleGridLines, int nSeries)
        {
            double width;
            if (nSeries > 1)
            {
                List<Point> temp = new List<Point>();
                double[] tempy = new double[DataList[0].LineSeries.Points.Count];

                foreach (BarDataSeries ds in DataList)
                {
                    width = chartStyleGridLines.YTick * ds.BarWidth;

                    for (int i = 0; i < ds.LineSeries.Points.Count; i++)
                    {
                        if (temp.Count > 0)
                        {
                            tempy[i] += temp[i].X;
                        }
                        DrawHorizontalBar(ds.LineSeries.Points[i], chartStyleGridLines, ds, width, tempy[i]);
                    }
                    temp.Clear();
                    temp.AddRange(ds.LineSeries.Points);
                }
            }
        }

        private Polygon DrawVerticalBar(Point point, ChartStyleGridLines chartStyleGridLines, BarDataSeries dataSeries, double width, double y)
        {
            Polygon plg = new Polygon();
            plg.Fill = dataSeries.FillColor;
            plg.Stroke = dataSeries.BorderColor;
            plg.StrokeThickness = dataSeries.BorderThickness;

            double x = point.X - 0.5 * chartStyleGridLines.XTick;
            plg.Points.Add(chartStyleGridLines.NormalizePoint(new Point(x - width / 2, y)));
            plg.Points.Add(chartStyleGridLines.NormalizePoint(new Point(x + width / 2, y)));
            plg.Points.Add(chartStyleGridLines.NormalizePoint(new Point(x + width / 2, y + point.Y)));
            plg.Points.Add(chartStyleGridLines.NormalizePoint(new Point(x - width / 2, y + point.Y)));
            DrawVerticalValueLabels(point, plg, chartStyleGridLines);
            return plg;
        }

        private void DrawVerticalBar1(Point point, ChartStyleGridLines chartStyleGridLines, BarDataSeries dataSeries, int nSeries, int n)
        {
            Polygon plg = new Polygon();
            plg.Fill = dataSeries.FillColor;
            plg.Stroke = dataSeries.BorderColor;
            plg.StrokeThickness = dataSeries.BorderThickness;

            double width = 0.7 * chartStyleGridLines.XTick;
            double w1 = width / nSeries;
            double w = dataSeries.BarWidth * w1;
            double space = (w1 - w) / 2;
            double x = point.X - 0.5 * chartStyleGridLines.XTick;
            plg.Points.Add(chartStyleGridLines.NormalizePoint(new Point(x - width / 2 + space + n * w1, 0)));
            plg.Points.Add(chartStyleGridLines.NormalizePoint(new Point(x - width / 2 + space + n * w1 + w, 0)));
            plg.Points.Add(chartStyleGridLines.NormalizePoint(new Point(x - width / 2 + space + n * w1 + w, point.Y)));
            plg.Points.Add(chartStyleGridLines.NormalizePoint(new Point(x - width / 2 + space + n * w1, point.Y)));
            DrawVerticalValueLabels(point, plg, chartStyleGridLines);
        }

        private void DrawVerticalValueLabels(Point point, Polygon plg, ChartStyleGridLines chartStyleGridLines)
        {
            TextBlock tb = new TextBlock() { Text = point.Y.ToString(CultureInfo.CurrentCulture),FontSize = this.LabelFontSize };
            if (!labelValuesVisibility)
            {
                tb.Visibility = Visibility.Collapsed;   
            }
            
            AddToolTipCheck(tb, tb.Text);
            AddToolTipCheck(plg, tb.Text);

            tb.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));

            chartStyleGridLines.ChartCanvas.Children.Add(plg);
            chartStyleGridLines.ChartCanvas.Children.Add(tb);
            Canvas.SetZIndex(tb, 9999);

            double offset = 0;
            double heightOffset = 0;
            bool labelHeight = false; double labelHeighta = 0;
            while (tb.DesiredSize.Width > (plg.Points[1].X - plg.Points[0].X) && tb.FontSize > labelMinimumFontSize && tb.FontSize > 2)
            {
                labelHeight = false;
                try
                {
                    tb.FontSize = tb.FontSize - .5;
                    tb.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));
                }
                catch (System.ArgumentException)
                {
                    break;
                }
            }

            if (tb.DesiredSize.Width > (plg.Points[1].X - plg.Points[0].X))
            {
                labelHeight = true; labelHeighta = tb.DesiredSize.Height;
                while (tb.DesiredSize.Height > (plg.Points[1].X - plg.Points[0].X) && tb.FontSize > labelMinimumFontSize && tb.FontSize > 2)
                {
                    try
                    {
                        tb.FontSize = tb.FontSize - .5;
                        tb.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));
                    }
                    catch (Exception)
                    {
                        break;
                    }
                }
                RotateTransform rotateTransform2 =
                             new RotateTransform(270, tb.RenderTransformOrigin.X, tb.RenderTransformOrigin.Y);
                tb.RenderTransform = rotateTransform2;
                offset = (tb.DesiredSize.Height - (plg.Points[1].X - plg.Points[0].X)) / 2;
                heightOffset = -tb.DesiredSize.Height / 4;
            }
            else
            {
                labelHeight = false;
                offset = (tb.DesiredSize.Width - (plg.Points[1].X - plg.Points[0].X)) / 2;
                heightOffset = -tb.DesiredSize.Height;
            }



            double temp = heightOffset;
            foreach (UIElement item in chartStyleGridLines.ChartCanvas.Children)
            {
                if (item is TextBlock)
                {
                    double left = Canvas.GetLeft(item);
                    double top = Canvas.GetTop(item);


                    if (top < (plg.Points[3].Y - 4) && left <= plg.Points[1].X && left >= plg.Points[0].X)
                    { heightOffset = temp; }

                    while (((top < (plg.Points[3].Y - 4))) && left <= plg.Points[1].X && left >= plg.Points[0].X)
                    {
                        heightOffset -= .1;
                        top += .1;
                    }
                }

            }


            Canvas.SetLeft(tb, plg.Points[0].X - offset);
     //     Canvas.SetRight(tb, plg.Points[1].X);
     //     Canvas.SetBottom(tb, plg.Points[2].Y);
            Canvas.SetTop(tb, plg.Points[3].Y + heightOffset);            
        }

        private void DrawHorizontalBar(Point point, ChartStyleGridLines chartStyleGridLines, BarDataSeries dataSeries, double width, double x)
        {
            Polygon plg = new Polygon();
            plg.Fill = dataSeries.FillColor;
            plg.Stroke = dataSeries.BorderColor;
            plg.StrokeThickness = dataSeries.BorderThickness;

            double y = point.Y - 0.5 * chartStyleGridLines.YTick;
            plg.Points.Add(chartStyleGridLines.NormalizePoint(new Point(x, y - width / 2)));
            plg.Points.Add(chartStyleGridLines.NormalizePoint(new Point(x, y + width / 2)));
            plg.Points.Add(chartStyleGridLines.NormalizePoint(new Point(x + point.X, y + width / 2)));
            plg.Points.Add(chartStyleGridLines.NormalizePoint(new Point(x + point.X, y - width / 2)));

            //    chartStyleGridLines.ChartCanvas.Children.Add(plg);
            DrawHorizontalValueLabels(point, plg, chartStyleGridLines, x, width);
        }

        private void DrawHorizontalBar1(Point point, ChartStyleGridLines chartStyleGridLines, BarDataSeries dataSeries, int nSeries, int n)
        {
            Polygon plg = new Polygon();
            plg.Fill = dataSeries.FillColor;
            plg.Stroke = dataSeries.BorderColor;
            plg.StrokeThickness = dataSeries.BorderThickness;

            double width = 0.7 * chartStyleGridLines.YTick;
            double w1 = width / nSeries;
            double w = dataSeries.BarWidth * w1;
            double space = (w1 - w) / 2;
            double y = point.Y - 0.5 * chartStyleGridLines.YTick;
            plg.Points.Add(chartStyleGridLines.NormalizePoint(new Point(0, y - width / 2 + space + n * w1)));
            plg.Points.Add(chartStyleGridLines.NormalizePoint(new Point(0, y - width / 2 + space + n * w1 + w)));
            plg.Points.Add(chartStyleGridLines.NormalizePoint(new Point(point.X, y - width / 2 + space + n * w1 + w)));
            plg.Points.Add(chartStyleGridLines.NormalizePoint(new Point(point.X, y - width / 2 + space + n * w1)));
            this.DrawHorizontalValueLabels(point, plg, chartStyleGridLines, 0, width);
        }

        private void DrawHorizontalValueLabels(Point point, Polygon plg, ChartStyleGridLines chartStyleGridLines, double x, double width)
        {
            TextBlock tb = new TextBlock() { Text = point.X.ToString(CultureInfo.CurrentCulture), FontSize = this.LabelFontSize };
            if (!labelValuesVisibility)
            {
                tb.Visibility = Visibility.Collapsed;
            }
            AddToolTipCheck(tb, tb.Text);
            AddToolTipCheck(plg, tb.Text);
            tb.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));

            chartStyleGridLines.ChartCanvas.Children.Add(plg);
            chartStyleGridLines.ChartCanvas.Children.Add(tb);
            Canvas.SetZIndex(tb, 9999);


            width = 1;
            double y = point.Y - 0.5 * chartStyleGridLines.YTick;
            Polygon pp = new Polygon();
            pp.Points.Add(chartStyleGridLines.NormalizePoint(new Point(x, y - width / 2)));
            pp.Points.Add(chartStyleGridLines.NormalizePoint(new Point(x, y + width / 2)));
            pp.Points.Add(chartStyleGridLines.NormalizePoint(new Point(x + point.X, y + width / 2)));
            pp.Points.Add(chartStyleGridLines.NormalizePoint(new Point(x + point.X, y - width / 2)));

            var heightOffset = ((pp.Points[3].Y - pp.Points[2].Y - tb.DesiredSize.Height));

            while (tb.DesiredSize.Width > (plg.Points[3].X - plg.Points[0].X) && tb.FontSize > labelMinimumFontSize)
            {
                tb.FontSize = tb.FontSize - .1;
                tb.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));
            }
            while (tb.DesiredSize.Height > (plg.Points[3].Y - plg.Points[2].Y) && tb.FontSize > labelMinimumFontSize)
            {
                tb.FontSize = tb.FontSize - .1;
                tb.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));
            }

            Canvas.SetLeft(tb, pp.Points[3].X - tb.DesiredSize.Width);
            //Canvas.SetRight(tb, pp.Points[1].X);
            //Canvas.SetBottom(tb, pp.Points[2].Y);
            Canvas.SetTop(tb, pp.Points[2].Y + heightOffset / 2);
        }

        void AddToolTipCheck(FrameworkElement element, string info)
        {
            if (this.activeToolTip)
            {
                element.ToolTip = info;
            }
        }

    }

}
